﻿using System;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using NLog;
using SGDExchangeApi;
using SunnyTrader.Api;
using SunnyTrader.Common;
using TradeAssistant;

namespace QuantBox.XApi
{
    internal enum TradingEventType
    {
        ReqInsert,
        RspInsertOrder,
        ReqCancel,
        RspCancelOrder,
        ReturnTrade,
        ReturnOrder
    }

    internal class TradingEvent
    {
        public TradingEventType Type;
        public ApiRspInfo? RspInfo;
        public InputOrderInforField? InputOrder;
        public OrderInforField? Order;
        public TradeInforField? Trade;
        public OrderField? NewOrder;
        public string? CancelOrderId;
    }

    public class SgdTradeClient
    {
        internal IXSpi spi;
        internal BaseTradeApi? api;
        private AsynSocketClientEx? _client;

        internal Logger logger = LogManager.CreateNullLogger();
        internal SunnyTrader.Common.RspUserLoginField sgdLoginField;
        private Task? _reconnectTask;
        private readonly CancellationTokenSource _reconnectCts = new();
        private StatusPublisher _publisher;
        private SgdQueryManager? _queryManager;
        private SgdDealProcessor? _processor;

        private int _orderRef;

        private static T? GetSocketClient<T>(IReflect type, object instance) where T : class
        {
            foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
            {
                if (field.FieldType == typeof(T))
                {
                    return (T)field.GetValue(instance);
                }
            }

            return default;
        }

        //private int GetNextRequestId()
        //{
        //    return Interlocked.Increment(ref _requestId);
        //}

        private int GetNextOrderRef()
        {
            return Interlocked.Increment(ref _orderRef);
        }

        private void SendError(ErrorField error)
        {
            Task.Run(() => spi.ProcessError(error));
        }
        internal void SendError(ref ApiRspInfo info, string source)
        {
            logger.Error($"{source}, {info.ErrorMsg}");
            SendError(new ErrorField(info.ErrorID, info.ErrorID, info.ErrorMsg, source));
        }
        private void SendError(string msg)
        {
            logger.Error(msg);
            SendError(new ErrorField(-1, -1, msg, nameof(SgdTradeClient)));
        }

        private void InitHandler()
        {
            api!.RtnOrder += OnRtnOrder;
            api.RtnTrade += OnRtnTrade;
            api.RspOrderAction += OnRspOrderAction;
            api.RspOrderInsert += OnRspOrderInsert;

            api.RspQryOrder += OnRspQryOrder;
            api.RspQryTrade += OnRspQryTrade;
            api.RspQryInvestorPosition += OnRspQryInvestorPosition;
            api.RspQryTradingAccount += OnRspQryTradingAccount;
            api.RspQrySettlementInfo += OnRspQrySettlementInfo;

            api.Connected += OnConnected;
            api.DisConnected += OnDisconnected;
            api.RspUserLogin += OnRspUserLogin;
            api.RspUserLogout += OnRspUserLogout;
        }

        private void OnRspUserLogout(BaseTradeApi api, RspUserLogoutField userLogout, ApiRspInfo rspInfo, int requestId, bool last)
        {
            logger.Debug($"TraderUserLogout, {userLogout.UserID}, {(rspInfo.ErrorID == 0 ? string.Empty : rspInfo.ErrorMsg)}");
        }

        private void OnRspUserLogin(BaseTradeApi api, SunnyTrader.Common.RspUserLoginField userLogin, ApiRspInfo rspInfo, int requestId, bool last)
        {
            if (rspInfo.ErrorID == 0)
            {
                logger.Debug($"Trader login success");
                if (!Connected)
                {
                    sgdLoginField = userLogin;
                    UserLogin = new RspUserLoginField();
                    UserLogin.TradingDay = SgdConvert.GetDate(userLogin.TradingDay);
                    UserLogin.LoginTime = SgdConvert.GetTime(userLogin.LoginTime);
                    UserLogin.UserID = userLogin.UserID;
                    UserLogin.SessionID = $"{userLogin.FrontID}:{userLogin.SessionID}";
                    UserLogin.Text = string.IsNullOrEmpty(userLogin.MaxOrderLocalID) ? "1" : userLogin.MaxOrderLocalID;
                    _orderRef = int.Parse(UserLogin.Text);
                    _publisher.Post(ConnectionStatus.Logined, UserLogin);
                    Connected = true;
                    _publisher.Post(ConnectionStatus.Confirmed);
                    _publisher.Post(ConnectionStatus.Done, UserLogin);
                }
                else
                {
                    api.QryOrder();
                    logger.Debug($"同步查询订单");
                    api.QryTrade();
                    logger.Debug($"同步查询成交");
                }
            }
            else
            {
                SendError(ref rspInfo, nameof(OnRspUserLogin));
                _publisher.Post(ConnectionStatus.Disconnected);
            }
        }

        private void OnRspQrySettlementInfo(BaseTradeApi api, string arg2, ApiRspInfo rspInfo, int requestId, bool last)
        {
        }

        private void OnRspQryTradingAccount(BaseTradeApi api, TradingAccountField account, ApiRspInfo rspInfo, int requestId, bool last)
        {
            _queryManager!.Post(QueryType.ReqQryTradingAccount, new QueryResponse { Account = account, RspInfo = rspInfo, IsLast = last });
        }

        private void OnRspQryInvestorPosition(BaseTradeApi api, InvestorPositionField position, ApiRspInfo rspInfo, int requestId, bool last)
        {
            _queryManager!.Post(QueryType.ReqQryInvestorPosition, new QueryResponse { Position = position, RspInfo = rspInfo, IsLast = last });
        }

        private void OnRspQryTrade(BaseTradeApi api, TradeInforField trade, ApiRspInfo rspInfo, int requestId, bool last)
        {
            logger.Debug($"成交查询回报, {trade.UserID}, ref:{trade.OrderRef}, oid:{trade.OrderSysID}, tid:{trade.TradeID}");
            _processor!.Post(new TradingEvent { Type = TradingEventType.ReturnTrade, Trade = trade });
        }

        private void OnRspQryOrder(BaseTradeApi api, OrderInforField order, ApiRspInfo rspInfo, int requestId, bool last)
        {
            logger.Debug($"订单查询回报, {order.UserID}, ref:{order.OrderRef}, oid:{order.OrderSysID}");
            _processor!.Post(new TradingEvent { Type = TradingEventType.ReturnOrder, Order = order });
        }

        private void OnRspOrderInsert(BaseTradeApi api, InputOrderInforField inputOrder, ApiRspInfo rspInfo, int requestId, bool last)
        {
            logger.Debug($"报单回报, {inputOrder.UserID}, ref:{inputOrder.OrderRef}, {rspInfo.ErrorMsg}");
            _processor!.Post(new TradingEvent { Type = TradingEventType.RspInsertOrder, InputOrder = inputOrder, RspInfo = rspInfo });
        }

        private void OnRspOrderAction(BaseTradeApi api, OrderInforField order, ApiRspInfo rspInfo, int requestId, bool last)
        {
            _processor!.Post(new TradingEvent { Type = TradingEventType.RspCancelOrder, Order = order, RspInfo = rspInfo });
        }

        private void OnRtnTrade(BaseTradeApi api, TradeInforField trade)
        {
            _processor!.Post(new TradingEvent { Type = TradingEventType.ReturnTrade, Trade = trade });
        }

        private void OnRtnOrder(BaseTradeApi api, OrderInforField order)
        {
            _processor!.Post(new TradingEvent { Type = TradingEventType.ReturnOrder, Order = order });
        }

        private void OnDisconnected(BaseTradeApi api, int reason)
        {
            logger.Debug($"TraderDisconnected,reason:{reason}");
            if (Connected && _reconnectTask == null)
            {
                logger.Debug("意外中断,延时重新连接");
                _reconnectTask = Task
                    .Delay(TimeSpan.FromSeconds(1), _reconnectCts.Token)
                    .ContinueWith(Reconnect, TaskContinuationOptions.OnlyOnRanToCompletion);
            }

            void Reconnect(Task t)
            {
                try
                {
                    logger.Debug("断线重连");
                    _reconnectTask = null;
                    if (!_reconnectCts.IsCancellationRequested)
                    {
                        api.Connect();
                    }
                }
                catch (Exception)
                {
                    // ignored
                }
            }
        }

        private void OnConnected(BaseTradeApi api)
        {
            logger.Debug($"TraderConnected");
            if (!Connected)
            {
                _publisher.Post(ConnectionStatus.Connected);
            }

            api.UserLogin();
        }

        public SgdTradeClient(IXSpi spi)
        {
            this.spi = spi;
            _publisher = new StatusPublisher(spi);
        }

        public void RegisterSpi(IXSpi spi)
        {
            this.spi = spi;
            _publisher = new StatusPublisher(spi);
        }

        public bool Connected { get; private set; }
        public ServerInfoField? Server { get; private set; }
        public UserInfoField? User { get; private set; }
        public RspUserLoginField? UserLogin { get; private set; }

        private bool CheckSettings(ServerInfoField server, UserInfoField user)
        {
            if (string.IsNullOrEmpty(server.Address))
            {
                SendError("没有设置 Address");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            if (string.IsNullOrEmpty(server.BrokerID))
            {
                SendError("没有设置 BrokerID");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            if (string.IsNullOrEmpty(user.UserID) || string.IsNullOrEmpty(user.Password))
            {
                SendError("没有设置用户名和密码");
                _publisher.Post(ConnectionStatus.Disconnected);
                return false;
            }
            return true;
        }

        public void Init(ServerInfoField server, UserInfoField user)
        {
            if (api != null)
            {
                return;
            }
            if (!CheckSettings(server, user))
            {
                return;
            }
            logger = LogManager.GetLogger($"Sgd.{user.UserID}");

            _queryManager = new SgdQueryManager(this);
            _processor = new SgdDealProcessor(this);
            User = user;
            Server = server;
            if (server.IsUsingUdp)
            {
                api = new TradeApi_SgdFuturesExchange();
                _client = GetSocketClient<AsynSocketClientEx>(typeof(TradeApi_SGDExchange), api);
            }
            else
            {
                api = new TradeApi_Assistant();
                _client = GetSocketClient<AsynSocketClientEx>(typeof(TradeApi_Assistant), api);
            }
            
            InitHandler();
            _publisher.Post(ConnectionStatus.Connecting);

            var info = new TradeApiLoginInforField();
            info.UserID = user.UserID;
            info.Password = user.Password;
            info.CtpBrokerID = server.BrokerID;
            info.ServerAddr = server.Address;
            info.ApiType = server.IsUsingUdp ? EnumTradeApiType.期货模拟 : EnumTradeApiType.SGD交易助手;
            info.GenericField = server.ExtInfoChar128 ?? string.Empty;
            api.Initialize(info);
            api.Connect();

            //string GetGenericField()
            //{
            //    if (server.IsUsingUdp)
            //    {
            //        return string.Empty;
            //    }

            //    if (string.IsNullOrEmpty(server.AuthCode))
            //    {
            //        return $"ApiType=期货模拟;ServerAddr={server.ExtInfoChar128};";
            //    }
            //    return $"ApiType=CTP期货;ServerAddr={server.ExtInfoChar128};AuthCode={server.AuthCode};";
            //}
        }

        internal void SimExchangeSendInputOrder(ref InputOrderInforField order)
        {
            var data = new CInputOrderField();
            try
            {
                data.BrokerID = sgdLoginField.BrokerID;
                data.OrderRef = order.OrderRef;
                data.UserID = sgdLoginField.UserID;
                data.CombHedgeFlag = EnumHedgeFlagType.Speculation;
                data.InstrumentID = order.Code;
                data.CombOffsetFlag = order.Offset;
                data.Direction = order.Direction;
                data.LimitPrice = order.LimitPrice;
                data.VolumeTotalOriginal = order.VolumeTotalOriginal;
                data.VolumeTotalOriginalDecimal = order.VolumeTotalOriginalDecimal == 0m ? order.VolumeTotalOriginal : order.VolumeTotalOriginalDecimal;
                var packageEx = new SGDExchangeApi.MessagePackageEx();
                packageEx.msgType = EnumMsgType.OrderInsert;
                packageEx.MsgObject = PublicFunction.ObjectToBytes(data);
                byte[] byteData = PublicFunction.ObjectToBytes(packageEx);
                _client!.Send(byteData);
            }
            catch (Exception)
            {
                logger.Debug(Global.ObjectToJson(data));
                throw;
            }
        }

        internal int AssistantSendInputOrder(ref InputOrderInforField order)
        {
            var data = new TAInputOrderField();
            try
            {
                data.BrokerID = Server!.BrokerID;
                data.OrderRef = order.OrderRef;
                data.UserID = User!.UserID;
                data.InstrumentID = order.Code;
                data.LimitPrice = order.LimitPrice;
                data.VolumeTotalOriginal = order.VolumeTotalOriginal;
                data.VolumeTotalOriginalDecimal = order.VolumeTotalOriginalDecimal == 0m
                    ? order.VolumeTotalOriginal
                    : order.VolumeTotalOriginalDecimal;
                data.Direction = order.Direction;
                data.HedgeFlag = EnumHedgeFlagType.Speculation;
                data.SessionID = api!.SessionID;
                data.OffsetFlag = order.Offset;
                var package = new TradeAssistant.MessagePackageEx();
                package.msgType = EnumMsgType.OrderInsert;
                package.MsgObject = Global.ObjectToBytes(data);
                byte[] byteData = Global.ObjectToBytes(package);
                return _client!.Send(byteData);
            }
            catch (Exception)
            {
                logger.Debug(Global.ObjectToJson(data));
                throw;
            }
        }

        internal int InternalSend(ref InputOrderInforField order)
        {
            try
            {
                if (Server!.IsUsingUdp)
                {
                    SimExchangeSendInputOrder(ref order);
                }
                else
                {
                    AssistantSendInputOrder(ref order);
                }

                return 0;
            }
            catch (Exception e)
            {
                logger.Debug(Global.ObjectToJson(order));
                logger.Error(e);
                return -1;
            }
        }

        public void Release()
        {
            if (api != null)
            {
                _publisher.Post(ConnectionStatus.Releasing);
                _queryManager!.Close();
                _queryManager = null;
                Connected = false;
                WaitReconnect();
                api.DisConnect();
                api = null;
                _processor!.Close();
                _processor = null;
                _publisher.Post(ConnectionStatus.Disconnected);
            }

            void WaitReconnect()
            {
                _reconnectCts.Cancel();
                if (_reconnectTask != null)
                {
                    try
                    {
                        _reconnectTask.Wait(TimeSpan.FromSeconds(10));
                    }
                    catch (Exception)
                    {
                        // ignored
                    }
                }
            }
        }

        public string SendOrder(OrderField order)
        {
            if (!Connected)
            {
                return string.Empty;
            }

            if (string.IsNullOrEmpty(order.LocalID))
            {
                order.LocalID = GetNextOrderRef().ToString();
            }
            _processor!.Post(new TradingEvent { Type = TradingEventType.ReqInsert, NewOrder = order });
            return order.ID;
        }

        public void CancelOrder(string id)
        {
            if (Connected)
            {
                _processor!.Post(new TradingEvent { Type = TradingEventType.ReqCancel, CancelOrderId = id });
            }
        }

        public void Query(QueryType type, ReqQueryField field)
        {
            if (Connected)
            {
                if (type == QueryType.ReqQryInstrument)
                {
                    spi.ProcessQryInstrument(null, true);
                    return;
                }
                _queryManager!.Post(type, field);
            }
        }
    }
}